import os
import math
import time
import pygame as pg

pg.init()
pg.font.init()
font = pg.font.SysFont('Times New Roman', 10)
color = pg.Color('dodgerblue2')

class Neuron:
	def __init__(self,win):
		self.w1 = int.from_bytes(os.urandom(1),'big')/255.0
		self.w2 = int.from_bytes(os.urandom(1),'big')/255.0
		self.b = int.from_bytes(os.urandom(1),'big')/255.0
		# self.w1 = 0.5
		# self.w2 = 0.5
		# self.b = 0.5

		self.x1 = 0
		self.x2 = 0
		self.y = 0.0

		self.samples = []
		self.n1_samples = 0
		self.n0_samples = 0
		self.n_samples = 0

		self.rate = 0.8
		self.win = win
		# self.display = display

	def reset(self):
		self.w1 = 0.5
		self.w2 = 0.5
		self.b = 0.5

		self.x1 = 0
		self.x2 = 0
		self.y = 0.0

		self.samples.clear()
		self.n1_samples = 0
		self.n0_samples = 0
		self.n_samples = 0

	def active(self,x1,x2):
		self.x1 = x1
		self.x2 = x2

		# the large exp() will cause error "ValueError: math domain error", so need to limit s
		s =  self.w1 * self.x1 + self.w2 * self.x2 + self.b
		if(s > 10.0):
			s = 10.0
		elif(s < -10.0):
			s = -10.0

		self.y = 1/(1+math.exp(-s))
		return self.y

	def get(self,x1,x2):
		self.active(x1,x2)
		return self.y>0.5

	def show(self):
		# global display
		# display.text(str(self.x),60,2,1)
		text = font.render(str(self.y), True, color)
		self.win.blit(text, (70, 2))
		# self.display.text(str(self.y),70,2,1)

	def feed(self,x1,x2,y):
		if(y == 1 and self.n1_samples <10):
			self.n1_samples +=1
			self.n_samples += 1
			self.samples.append([x1,x2,y])
			print(x1,x2,",1")
		elif(y == 0 and self.n0_samples <10):
			self.n0_samples +=1
			self.n_samples += 1
			self.samples.append([x1,x2,y])
			print(x1,x2,",0")

		if(self.n_samples < 20):
			return False
		else:
			self.train()
			self.samples.clear()
			self.n_samples = 0
			self.n1_samples = 0
			self.n0_samples = 0

			return True # finish train

	def train(self):
		# self.display.fill(0)
		# self.display.text('Training...', 10, 20, 1)
		# # display.text('Touch to start', 10, 45, 1)
		# self.display.show()

		n_sample = len(self.samples)

		# iteration
		for n in range(100):
			grad_w1 = 0.0
			grad_w2 = 0.0
			grad_b = 0.0
			costs = 0.0
			grad = 0.0
			#calculate gradient average
			for i,s in enumerate(self.samples):
				predict_y = self.active(s[0],s[1])
				if(s[2]): # label = 1
					grad = predict_y - 1
					if(predict_y>4e-44): # log(0) : math domain error
						costs -= math.log(predict_y)
					else:
						costs -= -99.9
				else: # label = 0
					grad = predict_y - 0
					if(1-predict_y > 4e-44): # log(0) : math domain error
						costs -= math.log(1-predict_y)
					else:
						costs -= -99.9
				grad_w1 += s[0] * grad # for weight 1
				grad_w2 += s[1] * grad # for weight 2
				grad_b += 1.0  * grad # for bias, weight 0
			grad_w1 /= n_sample # average
			grad_w2 /= n_sample # average
			grad_b /= n_sample # average
			costs /= n_sample
			# update parameters
			self.w1 -= self.rate * grad_w1
			self.w2 -= self.rate * grad_w2
			self.b  -= self.rate * grad_b
			# show cost
			self.win.fill((255, 255, 255))
			# self.display.fill(0)
			text1 = font.render("Train... E:", True, color)
			self.win.blit(text1, (10, 0))
			# self.display.text("Train... E:", 10, 0, 1)

			text2 = font.render(str(n), True, color)
			self.win.blit(text2, (90, 0))
			# self.display.text(str(n), 90, 0, 1)

			text3 = font.render("Costs=", True, color)
			self.win.blit(text3, (10, 10))
			# self.display.text("Costs=", 10, 10, 1)

			text4 = font.render(str(costs), True, color)
			self.win.blit(text4, (60, 10))
			# self.display.text(str(costs), 60, 10, 1)
			# self.display.text("w1,w2,b =", 10, 20, 1)
			print(costs,self.w1,self.w2,self.b)

			text5 = font.render("w1=", True, color)
			self.win.blit(text5, (10, 30))
			# self.display.text("w1=", 10, 30, 1)

			text6 = font.render(str(self.w1), True, color)
			self.win.blit(text6, (50, 30))
			# self.display.text(str(self.w1), 50, 30, 1)

			text7 = font.render("w2=", True, color)
			self.win.blit(text7, (10, 40))
			# self.display.text("w2=", 10, 40, 1)

			text8 = font.render(str(self.w2), True, color)
			self.win.blit(text8, (50, 40))
			# self.display.text(str(self.w2), 50, 40, 1)

			text9 = font.render(" b=", True, color)
			self.win.blit(text9, (10, 50))
			# self.display.text(" b=", 10, 50, 1)

			text10 = font.render(str(self.b), True, color)
			self.win.blit(text10, (50, 50))
			# self.display.text(str(self.b), 50, 50, 1)

			pg.display.update()
			# self.display.show()
		time.sleep(3)
		#self.plot()

	# plot the curve of the neural x:distance y:jump or not
	def plot(self):
		self.win.fill((255, 255, 255))
		# self.display.fill(0)

		text1 = font.render("w1,w2,b =", True, color)
		self.win.blit(text1, (10, 12))
		# self.display.text("w1,w2,b =", 10, 12, 1)

		text2 = font.render(str(self.w1), True, color)
		self.win.blit(text2, (20, 22))
		# self.display.text(str(self.w1), 20, 22, 1)

		text3 = font.render(str(self.w2), True, color)
		self.win.blit(text3, (20, 32))
		# self.display.text(str(self.w2), 20, 32, 1)

		text4 = font.render(str(self.b), True, color)
		self.win.blit(text4, (20, 42))
		# self.display.text(str(self.b), 20, 42, 1)

		# plot distance axis
		pg.draw.line(self.win, (0, 63), (0, 128), 1)
		# self.display.hline(0,63,128,1)

		# plot curve
		# for x in range(128):
		# 	y = 63 - int(self.active(x/100)*20)
		# 	self.display.pixel(x,y,1)
		pg.display.update()
		# self.display.show()
		time.sleep(8)
